/*
 * Copyright (c) 2010, Jim Hollinger
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of Jim Hollinger nor the names of its contributors
 *     may be used to endorse or promote products derived from this
 *     software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <stdarg.h>
#include <iomanip>
#include <string>

#include "debughelper/debughelper.h"
#include "logger/logger.h"
#include "logger/loggerfactory.h"
#include "log4cplus/logger.h"
#include "log4cplus/configurator.h"


using namespace logger;


Logger::Logger() : myLogger(NULL) {}

Logger::~Logger() {
#if defined (WIN32)
    delete myLogger;
#else
    log4cplus::Logger *logger = (log4cplus::Logger *)myLogger;
    delete logger;
#endif
    myLogger = NULL;
}

// static
bool Logger::initialize(const std::string &name) {
    bool flag = false;

    Logger *theLogger = LoggerFactory::getInstance();
    if (theLogger == NULL) {
        fprintf(stderr, "Logger.initialize: Unable to create logger\n");
    } else {
        log4cplus::BasicConfigurator config;
        config.configure();

        theLogger->myLogger =
            new log4cplus::Logger(log4cplus::Logger::getInstance(name));
        theLogger->setLogLevel(log4cplus::ALL_LOG_LEVEL);
        flag = true;
    }

    return flag;
}

// static
Logger *Logger::getInstance() {
    return LoggerFactory::getInstance();
}

// static
void Logger::cleanup() {
    LoggerFactory::cleanup();
    log4cplus::Logger::shutdown();
}

// static
void Logger::unitTest() {
    Logger::initialize("test");
    Logger *logger = Logger::getInstance();
    logger->info("Begining Logger::unitTest, %d logs to follow", 6);
    int i = 1;
    logger->trace("%2d: %s log", i++, "Trace");
    logger->debug("%2d: %s log", i++, "Debug");
    logger->info ("%2d: %s log", i++, "Info ");
    logger->warn ("%2d: %s log", i++, "Warn ");
    logger->error("%2d: %s log", i++, "Error");
    logger->fatal("%2d: %s log", i++, "Fatal");
    logger->setLogLevel(log4cplus::INFO_LOG_LEVEL);
    logger->trace("%2d: This log should be filtered out", i);
    logger->debug("%2d: This log should also be filtered out", i);
    logger->info ("Begining LoggerStream tests, %d logs to follow", 3);
    LoggerClog myclog(logger);
    LoggerCout mycout(logger);
    LoggerCerr mycerr(logger);
    logger->setLogLevel(log4cplus::ALL_LOG_LEVEL);
    myclog << std::setw(2) << i++ << ':' << " Stream clog log" << std::endl;
    mycout << std::setw(2) << i++ << ':' << " Stream cout log" << std::endl;
    mycerr << std::setw(2) << i++ << ':' << " Stream cerr log" << std::endl;
    logger->setLogLevel(log4cplus::ERROR_LOG_LEVEL);
    mycout << "This stream clog log should be filtered out" << std::endl;
    logger->setLogLevel(log4cplus::INFO_LOG_LEVEL);
    logger->info ("Finished Logger::unitTest, %d logs should have appeared\n", 9);
    Logger::cleanup();
}

void Logger::setLogLevel(int logLevel) {
    log4cplus::Logger &logger = *(log4cplus::Logger *)myLogger;
    logger.setLogLevel(logLevel);
}

void Logger::trace(const char *format, ...) {
    log4cplus::Logger &logger = *(log4cplus::Logger *)myLogger;
    if (logger.isEnabledFor(log4cplus::TRACE_LOG_LEVEL)) {
        char buffer[1024];
        buffer[sizeof(buffer) - 1] = '\0';
        va_list arg;
        va_start(arg, format);
        vsnprintf(buffer, sizeof(buffer) - 1, format, arg);
        logger.forcedLog(log4cplus::TRACE_LOG_LEVEL, buffer);
        va_end(arg);
    }
}

void Logger::debug(const char *format, ...) {
    log4cplus::Logger &logger = *(log4cplus::Logger *)myLogger;
    if (logger.isEnabledFor(log4cplus::DEBUG_LOG_LEVEL)) {
        char buffer[1024];
        buffer[sizeof(buffer) - 1] = '\0';
        va_list arg;
        va_start(arg, format);
        vsnprintf(buffer, sizeof(buffer) - 1, format, arg);
        logger.forcedLog(log4cplus::DEBUG_LOG_LEVEL, buffer);
        va_end(arg);
    }
}

void Logger::info(const char *format, ...) {
    log4cplus::Logger &logger = *(log4cplus::Logger *)myLogger;
    if (logger.isEnabledFor(log4cplus::INFO_LOG_LEVEL)) {
        char buffer[1024];
        buffer[sizeof(buffer) - 1] = '\0';
        va_list arg;
        va_start(arg, format);
        vsnprintf(buffer, sizeof(buffer) - 1, format, arg);
        logger.forcedLog(log4cplus::INFO_LOG_LEVEL, buffer);
        va_end(arg);
    }
}

void Logger::warn(const char *format, ...) {
    log4cplus::Logger &logger = *(log4cplus::Logger *)myLogger;
    if (logger.isEnabledFor(log4cplus::WARN_LOG_LEVEL)) {
        char buffer[1024];
        buffer[sizeof(buffer) - 1] = '\0';
        va_list arg;
        va_start(arg, format);
        vsnprintf(buffer, sizeof(buffer) - 1, format, arg);
        logger.forcedLog(log4cplus::WARN_LOG_LEVEL, buffer);
        va_end(arg);
    }
}

void Logger::error(const char *format, ...) {
    log4cplus::Logger &logger = *(log4cplus::Logger *)myLogger;
    if (logger.isEnabledFor(log4cplus::ERROR_LOG_LEVEL)) {
        char buffer[1024];
        buffer[sizeof(buffer) - 1] = '\0';
        va_list arg;
        va_start(arg, format);
        vsnprintf(buffer, sizeof(buffer) - 1, format, arg);
        logger.forcedLog(log4cplus::ERROR_LOG_LEVEL, buffer);
        va_end(arg);
    }
}

void Logger::fatal(const char *format, ...) {
    log4cplus::Logger &logger = *(log4cplus::Logger *)myLogger;
    if (logger.isEnabledFor(log4cplus::FATAL_LOG_LEVEL)) {
        char buffer[1024];
        buffer[sizeof(buffer) - 1] = '\0';
        va_list arg;
        va_start(arg, format);
        vsnprintf(buffer, sizeof(buffer) - 1, format, arg);
        logger.forcedLog(log4cplus::FATAL_LOG_LEVEL, buffer);
        va_end(arg);
    }
}


LogStream::LogStream() : logger(NULL) {
    buf = new char[1024];
    buf_index = 0;
    buf[buf_index] = '\0';
}

LogStream::~LogStream() {
    delete []buf;
    buf = NULL;
}

void LogStream::setLogger(logger::Logger *dst, int level) {
    logger = dst;
    logLevel = level;
}

LogStream::int_type LogStream::overflow(int_type c) {
    if ((c == '\r') || (c == '\n') || (c == _Tr::eof())) {
        if (c == _Tr::eof()) {
            c = _Tr::not_eof(c);
        }
        if (logLevel <= log4cplus::DEBUG_LOG_LEVEL) {
            if (logger == NULL) {
                std::clog << buf_index << ':' << buf << '!' << std::endl;
            } else {
                logger->debug(buf);
            }
        } else if (logLevel <= log4cplus::WARN_LOG_LEVEL) {
            if (logger == NULL) {
                std::cout << buf_index << ':' << buf << '!' << std::endl;
            } else {
                logger->info(buf);
            }
        } else {
            if (logger == NULL) {
                std::cerr << buf_index << ':' << buf << '!' << std::endl;
            } else {
                logger->error(buf);
            }
        }
        buf_index = 0;
        buf[buf_index] = '\0';
    } else {
        buf[buf_index++] = static_cast<char> (c);
        buf[buf_index] = '\0';
    }

    return c;
}


LoggerStream::LoggerStream(Logger *logger, int logLevel) : std::ostream(&buf) {
    buf.setLogger(logger, logLevel);
}


LoggerClog::LoggerClog(logger::Logger *logger)
    : LoggerStream(logger, log4cplus::DEBUG_LOG_LEVEL) {}


LoggerCout::LoggerCout(logger::Logger *logger)
    : LoggerStream(logger, log4cplus::WARN_LOG_LEVEL) {}


LoggerCerr::LoggerCerr(logger::Logger *logger)
    : LoggerStream(logger, log4cplus::ERROR_LOG_LEVEL) {}
